/*
 * jngc, Java utility classes by Nicholas Coltharp
 * Copyright (C) 2010 Nicholas Coltharp <ngc1@rice.edu>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package edu.rice.ngc1.util.time;

import java.io.Serializable;

import edu.rice.ngc1.util.EqualsHelper;
import edu.rice.ngc1.util.HashCodeGenerator;

/**
 * <p>
 * Simple utility class used to time operations.
 * </p>
 * 
 * <p>
 * Note that since method calls always incur some overhead, this class
 * is not perfectly accurate. Unless the task you are timing is very
 * short, the inaccuracy should be negligible.
 * </p>
 */
public class Timer implements Cloneable, Serializable {

    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = 2815349649518820791L;

    /**
     * The time at which this timer was started.
     */
    private long startTime = 0;

    /**
     * The {@code TimeSource} that this timer will use to keep track
     * of time. Defaults to {@link SystemTimeSource#SINGLE}.
     */
    private final TimeSource time;

    /**
     * Constructs a new {@code Timer} which will get its time from
     * {@link SystemTimeSource#SINGLE} and calls {@link #start()}.
     */
    public Timer() {
        this(SystemTimeSource.SINGLE);
    }

    /**
     * Constructs a new {@code Timer} with the specified
     * {@code TimeSource} and calls {@link #start()}.
     * 
     * @param source
     *            the {@code TimeSource} to get time from
     */
    public Timer(TimeSource source) {
        this.time = source;
        start();
    }

    /**
     * Constructs a new {@code Timer} which uses the given timer's
     * {@code TimeSource} acts as if it had been started at the same
     * time that the given {@code Timer} was last started.
     * 
     * @param other
     *            a {@code Timer} to copy
     */
    public Timer(Timer other) {
        this.startTime = other.startTime;
        this.time = other.time;
    }

    @Override
    public boolean equals(Object o) {
        Timer t = EqualsHelper.check(this, o);
        if (t == null) {
            return false;
        }
        return ((t.startTime == this.startTime) && t.time.equals(this.time));
    }

    @Override
    public int hashCode() {
        int hash = HashCodeGenerator.getInitialValue();
        hash = HashCodeGenerator.hash(hash, startTime);
        hash = HashCodeGenerator.hash(hash, time);
        return hash;
    }

    @Override
    public Timer clone() {
        return new Timer(this);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("Timer(started=");
        builder.append(startTime);
        builder.append(")");
        return builder.toString();
    }

    /**
     * Gets the {@code TimeSource} that this object will keep time by.
     * 
     * @return this object's {@code TimeSource}
     */
    public TimeSource getTimeSource() {
        return time;
    }

    /**
     * Resets the timer and starts it at the current time.
     */
    public void start() {
        this.startTime = time.currentTimeMillis();
    }

    /**
     * Returns the amount of time elapsed, in milliseconds, since the
     * last call to {@link #start()}.
     * 
     * @return the amount of time elapsed
     */
    public long getElapsedTime() {
        return time.currentTimeMillis() - this.startTime;
    }

    /**
     * Measures and returns the amount of time it takes a given
     * {@code Runnable} to complete its task.
     * 
     * @param r
     *            the {@code Runnable} to run
     * @return the length of time it takes {@code r} to run
     */
    public static long time(Runnable r) {
        return time(r, SystemTimeSource.SINGLE);
    }

    /**
     * Measures the amount of time it takes a given {@code Runnable}
     * to run with respect with a given {@code TimeSource}.
     * 
     * @param r
     *            the {@code Runnable} to time
     * @param source
     *            the {@code TimeSource} to use
     * @return the amount of time it takes {@code r} to run with
     *         respect to {@code source}.
     */
    public static long time(Runnable r, TimeSource source) {
        Timer t = new Timer(source);
        r.run();
        return t.getElapsedTime();
    }
}
